# user/build.py

import enum
import os
import sys
from sys import prefix
from loguru import logger

os_qemu_addr = 0x80200000
os_k210_addr = 0x80020000
user_app_base_address = 0x80400000
step = 0x20000
user_linker = './user/src/linker.ld'
os_linker = './os/src/linker.ld'
prefix = '[build.py]'
k210_serial_port = "/dev/ttyUSB0"
k210_boot_loader = "./bootloader/rustsbi-k210.bin"
qemu_boot_loader = "./bootloader/rustsbi-qemu.bin"
kernel_bin = "./os/target/riscv64gc-unknown-none-elf/release/os"
k210_boot_loader_size = "131072"
rebuild = True
k210_mode = False
debug_mode = False


def exit_function():
    """
    exit_function some actions when exit
    """
    exit()


def build_apps(apps):
    """
    build_apps build user applications

    Args:
        apps (list): user application name list 
    """
    for app_id, app in enumerate(apps):
        lines = []
        lines_before = []
        with open(user_linker, 'r') as f:
            for line in f.readlines():
                lines_before.append(line)
                line = line.replace(hex(user_app_base_address),
                                    hex(user_app_base_address + step * app_id))
                lines.append(line)
        with open(user_linker, 'w+') as f:
            f.writelines(lines)
        if os.system(
                'cd user && cargo build --bin {} --release'.format(app)) != 0:
            logger.info('{} Error on running cargo build apps!'.format(prefix))
            exit_function()
        if os.system(
                'cd user && rust-objcopy --strip-all target/riscv64gc-unknown-none-elf/release/{0}  -O binary target/riscv64gc-unknown-none-elf/release/{0}.bin'
                .format(app)) != 0:
            logger.info('{} Error on running rust objcopy'.format(prefix))
            exit_function()
        logger.info('{} application {} start with address {}'.format(
            prefix, app, hex(user_app_base_address + step * app_id)))
        with open(user_linker, 'w+') as f:
            f.writelines(lines_before)
    logger.info('{} Building apps succeeded!'.format(prefix))


def build_os(isK210=False):
    """
    build_os build operation system
    """
    lines = []
    lines_before = []
    if isK210:
        with open(os_linker, "r") as f:
            for line in f.readlines():
                lines_before.append(line)
                line = line.replace(hex(os_qemu_addr), hex(os_k210_addr))
                lines.append(line)
        with open(os_linker, 'w+') as f:
            f.writelines(lines)

    if os.system('cd os && cargo build --release') != 0:
        logger.info('{} Error on running cargo build os'.format(prefix))
        exit_function()
    if os.system('rust-objcopy --strip-all {0} -O binary {0}.bin'.format(
            kernel_bin)) != 0:
        logger.info('{} Error on running rust objcopy'.format(prefix))
        exit_function()
    logger.info('{} Building OS succeeded!'.format(prefix))

    if isK210:
        with open(os_linker, 'w+') as f:
            f.writelines(lines_before)


def run_os():
    """
    run_os run operation system
    """
    if os.system(
            'qemu-system-riscv64 -machine virt -nographic -bios {} -device loader,file={}.bin,addr={}'
            .format(qemu_boot_loader, kernel_bin, hex(os_qemu_addr))) != 0:
        logger.info('{} Error on running qemu'.format(prefix))
        exit_function()


def debug_os():
    """
    debug_os debug operation system
    """
    if os.system(
            'qemu-system-riscv64 -machine virt -nographic -bios {} -device loader,file={}.bin,addr={} -s -S &'
            .format(qemu_boot_loader, kernel_bin, hex(os_qemu_addr))) != 0:
        logger.info('{} Error on running qemu'.format(prefix))
        exit_function()
    if os.system(
            "riscv64-unknown-elf-gdb -ex 'file {}' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'"
            .format(kernel_bin)) != 0:
        logger.info('{} Error on running gdb'.format(prefix))
        exit_function()


def generate_link_app(apps):
    """
    generate_link_app generate link_app assembly file

    Args:
        apps (list): user application list 
    """
    with open('os/src/link_app.S', 'w') as f:
        f.write("""# This assembly file is automatically generated by python
# os/src/link_app.S

    .align 3
    .section .data
    .global _num_app
_num_app:
""")
        f.write("    .quad {}\n".format(len(apps)))
        for i in range(len(apps)):
            f.write("    .quad app_{}_start\n".format(i))
        f.write("    .quad app_{}_end\n".format(len(apps) - 1))
        for i, app in enumerate(apps):
            f.write("""
    .section .data
    .global app_{0}_start
    .global app_{0}_end
app_{0}_start:
    .incbin "../user/target/riscv64gc-unknown-none-elf/release/{1}.bin"
app_{0}_end:
    """.format(i, app))
    logger.info('{} Generating link_app assembly file succeeded!'.format(prefix))


def discover_app():
    """
    discover_app automatically discover user applications in directory: user/src/bin

    Returns:
        list: user application list 
    """
    apps = os.listdir('user/src/bin')
    ret = []
    for app in sorted(apps):
        ret.append(app[:app.find('.')])
    return ret


def remove_target_directory():
    """
    remove_target_directory remove os target directory and user target directory
    """
    if os.system('rm -rf ./os/target') != 0:
        logger.info('{} Error on removing os target directory'.format(prefix))
        exit_function()
    if os.system("rm -rf ./user/target") != 0:
        logger.info('{} Error on removing user target directory'.format(prefix))
        exit_function()
    logger.info("{} Removing target directory succeeded!".format(prefix))


def burn_k210():
    """
    burn_k210 burn firmware to k210 dev board
    """
    if os.system("cp {0} {0}.copy".format(k210_boot_loader)) != 0:
        logger.info("{} Error on running cp!".format(prefix))
        exit_function()
    if os.system("dd if={}.bin of={}.copy bs={} seek=1".format(
            kernel_bin, k210_boot_loader, k210_boot_loader_size)) != 0:
        logger.info("{} Error on running dd!".format(prefix))
        exit_function()
    if os.system("mv {}.copy {}.bin".format(k210_boot_loader, kernel_bin)) != 0:
        logger.info("{} Error on running mv!".format(prefix))
        exit_function()
    if os.system("kflash -p {} -b 1500000 {}.bin".format(k210_serial_port,
                                                     kernel_bin)) != 0:
        logger.info("{} Error on running kflash!".format(prefix))
        exit_function()
    logger.info("{} Burning k210 succeeded!".format(prefix))


def run_k210():
    """
    run_k210 run firmware in k210
    """
    if os.system(
            "python3 -m serial.tools.miniterm --eol LF --dtr 0 --rts 0 --filter direct {} 115200"
            .format(k210_serial_port)) != 0:
        logger.info("{} Error on running serial.tools.miniterm!".format(prefix))
        exit_function()


def install():
    """
    install install myOS dev environment
    """
    with open("INSTALL", "r") as f:
        for line in f.readlines():
            command = line.strip()
            if command.startswith('#') or command.startswith('\n'):
                continue
            if os.system(command) != 0:
                logger.info("{} Error on running \"{}\"!", prefix, command)
                exit_function()
    logger.info("{} Installing environment succeeded!".format(prefix))


def __main__():
    """
    __main__ main function
    """
    if rebuild:
        remove_target_directory()
    apps = discover_app()
    generate_link_app(apps)
    build_apps(apps)
    build_os(k210_mode)
    if k210_mode:
        burn_k210()
        run_k210()
    else:
        if debug_mode:
            debug_os()
        else:
            run_os()


__main__()